﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
//using System.Text.RegularExpressions;
using System.Threading;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Net;
using System.Globalization;

namespace Innovox.YDeviceLib
{
    public enum DS2SocketStatus
    {
        NotConnected,
        WaitForConnection,
        Connected,
        TimedOut,
        Error,
        DataReceived
    }



    public class DS2Socket
    {
        static int defaultTimeout = 20000;
		int socketTimeout;
		public int SocketTimeout { get { return socketTimeout; } set { socketTimeout = value; } }
			
			// Fired when all pings complete
        public event EventHandler<EventArgs> DS2Received;

        private byte[] rxBuff;
        public Socket Msock { get; set; }
        public IPAddress Ip { get; set; }
        public int Port { get; set; }
        public DS2SocketStatus Status { get; set; }
        public string Response { get; set; }
        public string OutBuf { get; set; }
        const int rxBuxSize = 2048;
        public string Error { get; set; }
        public bool ResponseReady { get; set; }
        public ManualResetEvent AllDone { get; set; }
        public string Prompt { get; set; }
        public bool AutoClose { get; set; }

        public DS2Socket(IPAddress ip, int port, bool autoClose)
        {
            Ip = ip;
            Port = port;
            AutoClose = autoClose;
            socketTimeout = defaultTimeout;
        }

        public DS2Socket(IPAddress ip, int port, bool autoClose, int timeout)
        {
            Ip = ip;
            Port = port;
            AutoClose = autoClose;
            socketTimeout = timeout;
        }

        public bool Connect()
        {
            Status = DS2SocketStatus.NotConnected;
            rxBuff = new byte[rxBuxSize];
            Msock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Response = "";
            AllDone = new ManualResetEvent(false);

            BeginConnect();
            DS2Received = DataReceived;
            AllDone.WaitOne();
            //while (Status == DS2SocketStatus.Connected || Status == DS2SocketStatus.WaitForConnection)
            //    Thread.Sleep(10);

            if (Status == DS2SocketStatus.Connected)
                return true;

            return false;
        }

        public void Close()
        {
            if (Msock.Connected == true)
                Msock.Shutdown(SocketShutdown.Both);
            Msock.Close();
        }

        public bool Send(string cmd, string prompt, out string result)
        {
            Response = "";
            AllDone = new ManualResetEvent(false);

            result = "";
            Prompt = prompt;
            OutBuf = cmd;

            // Check we are connected
            if (Msock == null || !Msock.Connected)
            {
                Status = DS2SocketStatus.NotConnected;
                return false;
            }

            try
            {
                // Convert to byte array and send.
                Byte[] byteCmdLine = Encoding.ASCII.GetBytes(OutBuf.ToCharArray());//m_tbMessage.Text.ToCharArray());              
                //AsyncCallback onconnect = new AsyncCallback(OnConnect);
                Msock.Send(byteCmdLine,0, byteCmdLine.Length, 0);
                SetupReceiveCallback(this);
            }
            catch (Exception ex)
            {
                Error = (string)ex.Message.Clone();
                Status = DS2SocketStatus.Error;
                RaiseDS2Received(this);
                AllDone.Set();
            }

            AllDone.WaitOne();

            if (Status == DS2SocketStatus.DataReceived)
            {
                result = Response;
                return true;
            }

            return false;
        }

        public bool Send(string cmd)
        {
            OutBuf = cmd;
            // Check we are connected
            if (Msock == null || !Msock.Connected)
            {
                Status = DS2SocketStatus.NotConnected;
                return false;
            }

            try
            {
                // Convert to byte array and send.
                Byte[] byteCmdLine = Encoding.ASCII.GetBytes(OutBuf.ToCharArray());
                Msock.Send(byteCmdLine, 0, byteCmdLine.Length, 0);
            }
            catch (Exception ex)
            {
                Error = (string)ex.Message.Clone();
                Status = DS2SocketStatus.Error;
            }

            AllDone.WaitOne();

            if (Status == DS2SocketStatus.Error)
                return false;

            return true;
        }

        private static void DataReceived(object sender, EventArgs e)
        {
            DS2Socket dsock = (DS2Socket)sender;
            dsock.Status = DS2SocketStatus.DataReceived;
            dsock.AllDone.Set();
            //Console.WriteLine(dsock.Ip + " data received");
        }

        public void BeginConnect()
        {
            try
            {
                object data = new object();
                // Define the Server address and port
                IPEndPoint epServer = new IPEndPoint(Ip, Port);
                // Connect to server non-Blocking method
                Msock.Blocking = false;
                Status = DS2SocketStatus.WaitForConnection;

                AsyncCallback onconnect = new AsyncCallback(OnConnect);
                IAsyncResult result = Msock.BeginConnect(epServer, onconnect, this);
                ThreadPool.RegisterWaitForSingleObject(
                        result.AsyncWaitHandle,
                        new WaitOrTimerCallback(SocketTimeoutCallback),
                        this,
                        socketTimeout,
                        true
                        );
            }
            catch (Exception ex)
            {
                Error = (string) ex.Message.Clone();
                Status = DS2SocketStatus.Error;
                RaiseDS2Received(this);
            }
        }

        public void OnConnect(IAsyncResult ar)
        {
            DS2Socket dsock = (DS2Socket)ar.AsyncState;
            // Check if connection sucessfull
            try
            {
                if (dsock.Msock.Connected)
                {
                    Status = DS2SocketStatus.Connected;
                    AllDone.Set();
                }
                else
                {
                    if( Status != DS2SocketStatus.TimedOut )
                        Status = DS2SocketStatus.NotConnected;
                    RaiseDS2Received(dsock);
                }
            }
            catch (Exception ex)
            {
                Error = (string)ex.Message.Clone();
                Status = DS2SocketStatus.Error;
                RaiseDS2Received(dsock);
            }
        }

        private void SocketTimeoutCallback(object state, bool timedOut)
        {
            if (timedOut)
            {
                DS2Socket dsock = (DS2Socket)state;
                Status = DS2SocketStatus.TimedOut;
                RaiseDS2Received(dsock);
            }
        }

        private void RaiseDS2Received(DS2Socket dsock)
        {
            if (AutoClose)
                Close();

            if (DS2Received != null)
            {
                //Console.WriteLine(dsock.Ip.ToString() + " event: "+ dsock.Status.ToString());
                DS2Received(dsock, new EventArgs());
            }
        }
 
        public void SetupReceiveCallback(DS2Socket dsock)
        {
            if (dsock.Msock.Connected == true)
            {
                try
                {
                    AsyncCallback receiveData = new AsyncCallback(OnReceivedData);
                    IAsyncResult result = dsock.Msock.BeginReceive(rxBuff, 0, rxBuff.Length, SocketFlags.None, receiveData, dsock);
                    ThreadPool.RegisterWaitForSingleObject(
                        result.AsyncWaitHandle,
                        new WaitOrTimerCallback(SocketTimeoutCallback),
                        this,
                        socketTimeout,
                        true
                        );
                }
                catch (Exception ex)
                {
                    Error = (string)ex.Message.Clone();
                    Status = DS2SocketStatus.NotConnected;
                    RaiseDS2Received(dsock);
                    return;
                }
            }
        }

        public void OnReceivedData(IAsyncResult ar)
        {
            // Socket was the passed in object
            DS2Socket dsock= (DS2Socket)ar.AsyncState;
            // Check if we got any data
            try
            {
                int nBytesRec = dsock.Msock.EndReceive(ar);
                if (nBytesRec > 0)
                {
                    // Wrote the data to the List
                    Response += Encoding.ASCII.GetString(rxBuff, 0, nBytesRec);
                    if (Response.Contains(Prompt))
                        RaiseDS2Received(this);
                    else // If the connection is still usable restablish the callback
                        SetupReceiveCallback(dsock);
                }
                else
                {
                    if (OutBuf.Length > 0)
                        Status = DS2SocketStatus.DataReceived;
                    else
                        Status = DS2SocketStatus.NotConnected;

                    RaiseDS2Received(this);
                }
            }
            catch (Exception ex)
            {
                Error = (string)ex.Message.Clone();
                Status = DS2SocketStatus.Error;
                RaiseDS2Received(dsock);
            }
        }
    }
}
